// Copyright (c) 2010 Manuel Peinado Gallego <manuel.peinado@gmail.com>
// Distributed under the MIT license


#include "stdafx.h"
#include "WorldBuilder.h"
#include "LightNode.h"
#include "Sphere.h"
#include "View3d.h"
#include "AABB.h"
#include "Color.h"



// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -  
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -  

LightNode::LightNode()
: m_needsUpdating(true),
  m_id(-1)
{
    m_params.type = LightTypes::DIRECTIONAL;
    m_params.ambientColor = Color(.2f, .2f, .2f);
    m_params.diffuseColor = Color(.8f, .8f, .8f);
    m_params.specularColor.from_id(ColorIds::Grey192);
    m_params.isSpot = false;
    m_params.spotCutoff = 45;

    clearAttenuation();
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -  
    
LightParams LightNode::getParams() const 
{ 
    return m_params; 
}

void LightNode::setParams(const LightParams& newParams) 
{ 
    m_params = newParams;
    notifyPropertyChange(NodeEvent::LightNodeParam);
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -  

void LightNode::setSpotCutoff(int angle)
{
    ASSERT(isSpot());
    ASSERT(angle >= 0 && angle <= 90);
    m_params.spotCutoff = angle;
    notifyPropertyChange(NodeEvent::LightNodeParam);
}

int LightNode::getSpotCutoff() const
{
    ASSERT(isSpot());
    return m_params.spotCutoff;
}
    
bool LightNode::isSpot() const
{
    return m_params.isSpot;
}
 
void LightNode::setSpot(bool v)
{
    m_params.isSpot = v;
    notifyPropertyChange(NodeEvent::LightNodeParam);
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -  


void LightNode::setAmbientColor(const Color& color)
{
    m_params.ambientColor = color;
    m_needsUpdating = true;
    notifyPropertyChange(NodeEvent::LightNodeParam);
}

void LightNode::setDiffuseColor(const Color& color)
{
    m_params.diffuseColor = color;
    m_needsUpdating = true;
    notifyPropertyChange(NodeEvent::LightNodeParam);
}

void LightNode::setSpecularColor(const Color& color)
{
    m_params.specularColor = color;
    m_needsUpdating = true;
    notifyPropertyChange(NodeEvent::LightNodeParam);
}


// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -  

void LightNode::clearAttenuation()
{
    m_params.constantAttenuationFactor = 1.f;
    m_params.linearAttenuationFactor = .0f;
    m_params.quadraticAttenuationFactor = 0.f;
    notifyPropertyChange(NodeEvent::LightNodeParam);
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -  

void LightNode::setConstantAttenuationFactor(float newVal)
{
    if(newVal < 0.f)
    {
        throw std::invalid_argument("constant att. factor must be non-negative");
    }
    m_params.constantAttenuationFactor = newVal;
    notifyPropertyChange(NodeEvent::LightNodeParam);
}

void LightNode::setLinearAttenuationFactor(float newVal)
{
    if(newVal < 0.f)
    {
        throw std::invalid_argument("linear att. factor must be non-negative");
    }
    m_params.linearAttenuationFactor = newVal;
    notifyPropertyChange(NodeEvent::LightNodeParam);
}

void LightNode::setQuadraticAttenuationFactor(float newVal)
{
    if(newVal < 0.f)
    {
        throw std::invalid_argument("quadratic att. factor must be non-negative");
    }
    m_params.quadraticAttenuationFactor = newVal;
    notifyPropertyChange(NodeEvent::LightNodeParam);
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -  

float LightNode::getConstantAttenuationFactor() const
{
    return m_params.constantAttenuationFactor;
}

float LightNode::getLinearAttenuationFactor() const
{
    return m_params.linearAttenuationFactor;
}

float LightNode::getQuadraticAttenuationFactor() const
{
    return m_params.quadraticAttenuationFactor;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -  

Color LightNode::getAmbientColor() const
{
    return m_params.ambientColor;
}

Color LightNode::getDiffuseColor() const
{
    return m_params.diffuseColor;
}

Color LightNode::getSpecularColor() const
{
    return m_params.specularColor;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -  

void LightNode::setType(LightType type)
{
    m_params.type = type;
}

LightType LightNode::getType() const
{
    return m_params.type;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -  

bool LightNode::canBeDrawnImpl() const 
{ 
    return true; 
}

bool LightNode::isBoundedImpl() const
{
    return true;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -  

void LightNode::setId(int newId)
{
    m_id = newId;
    m_needsUpdating = true;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -  

void LightNode::updateParams()
{
    ASSERT(m_id != -1);

    if(m_needsUpdating)
    {
        glEnable(m_id);

        glLightfv(m_id, GL_AMBIENT, getAmbientColor().to_array());
        glLightfv(m_id, GL_DIFFUSE, getDiffuseColor().to_array());
        glLightfv(m_id, GL_SPECULAR, getSpecularColor().to_array());

        glLightf(m_id, GL_CONSTANT_ATTENUATION, getConstantAttenuationFactor());
        glLightf(m_id, GL_LINEAR_ATTENUATION, getLinearAttenuationFactor());
        glLightf(m_id, GL_QUADRATIC_ATTENUATION, getQuadraticAttenuationFactor());

        if(isSpot())
        {
            glLighti(m_id, GL_SPOT_CUTOFF, getSpotCutoff());
        }
        else
        {
            glLighti(m_id, GL_SPOT_CUTOFF, 180);
        }
    }

    // Params that depend on the camera are always updated

    float posOrDir[4];
    getPosOrDirVec(posOrDir);
    glLightfv(m_id, GL_POSITION, posOrDir);

    if(isSpot())
    {
        gx::Vec3 spotDir = getGlobalTransform().get_col_2();
        float dir[3] = { (float)spotDir[0], (float)spotDir[1], (float)spotDir[2] };
        glLightfv(m_id, GL_SPOT_DIRECTION, dir);
   }
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -  

void LightNode::draw(View3d* view) const
{
    double arrowLength = .5;  // Not only the head

    glPushAttrib(GL_CURRENT_BIT | GL_LIGHTING_BIT);
    glDisable(GL_LIGHTING);

    // Set the light's color

    SelectionManager& sm = view->getUi().getSelectionManager();
    bool hilight = view->getHoverItem() == this || sm.getSelection()== this;
    Color color = getYellow();
    if(hilight)
    {
        color = makeBrighter(color);
    }
    else
    {
        color = makeDarker(color);
    }

    setColorIfNotPicking(view, color);

    // Draw a tiny sphere where the light is

    glutSolidSphere(.1, 5, 5);

    // Draw some beans for a directional light (##)
    if(m_params.type == LightTypes::DIRECTIONAL)
    {
        gx::Vec3 lightDir = gx::K;
        glBegin(GL_LINES);
        for(int i = -1; i <= 1; ++i)
        {
            glVertex3d(0., i * .1, 0.);
            glVertex3dv((lightDir * arrowLength + gx::J * i * .1).to_array() );
        }
        glEnd();
    }

    // Draw the glow around an omni (positional, non-spot) light
    else    
    {
        if(!isSpot())
        {
            glBegin(GL_LINES);
                int N = 10;
                gx::Vec3 camX = view->getCameraMatrix().get_col_0();
                gx::Vec3 camY = view->getCameraMatrix().get_col_1();
                for(int i = 0; i < N; ++i)
                {
                    double theta = i / double(N) * TWO_PI;
                    double c = std::cos(theta);
                    double s = std::sin(theta);
                    gx::Vec3 v = c * camX + s * camY;
                    glVertex3dv((v * .1).to_array());
                    glVertex3dv((v * .2).to_array());
                }
            glEnd();
        }
    }

    // Draw arrow head for directional lights (this continues what
    // was started on (##) above)
    if(m_params.type == LightTypes::DIRECTIONAL)
    {
        glPushMatrix();

        glTranslated(0., 0., .8 * arrowLength);

        gx::Vec3 v = gx::J;

        for(int i = -1; i <= 1; ++i)
        {
            gx::Vec3 w = v * i * .1;
            glTranslated(w.x, w.y, w.z);
            glutSolidCone(.03, (1. - .8) * arrowLength, 10, 1);
            glTranslated(-w.x, -w.y, -w.z);
        }
        glPopMatrix();
    }
    else
    {
        // Draw beam for spot positional light
        if(isSpot())
        {
            glPushMatrix();

            glTranslated(0., 0., arrowLength);
            glRotatef(180.f, .0f, 1.f, .0f);

            double angleDegs = getSpotCutoff() * .5;
            double angleRads = degToRad(angleDegs);
            double height = arrowLength * std::tan(angleRads);


            glutWireCone(height, arrowLength, 6, 1);
            glPopMatrix();
        }
    }

    glPopAttrib();
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -  


AABB LightNode::getAabbLocalImpl() const
{
    // TODO make this depend on camera eye position
    return Sphere(gx::zero, 0.05).getAabb();
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -  

void LightNode::getPosOrDirVec(float dest[4]) const
{
    gx::Mat4 transform = getGlobalTransform();
    gx::Vec3 posOrDir;
    if(m_params.type == LightTypes::POSITIONAL)
    {
        posOrDir = transform.translation;
        dest[3] = 1.f;
    }
    else
    {
        // Negate it because OGL interprets it as
        // "direction in which the rays come from"
        posOrDir = -transform.get_col(2);
        dest[3] = 0.f;
    }

    dest[0] = (float)posOrDir.x;
    dest[1] = (float)posOrDir.y;
    dest[2] = (float)posOrDir.z;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Serialization

void LightNode::read(std::wistream& is)
{
    TransformNode::read(is);
    int type;
    is >> type >> m_params.ambientColor  >> m_params.diffuseColor 
       >> m_params.specularColor >> m_params.spotCutoff 
       >> m_params.constantAttenuationFactor >> m_params.linearAttenuationFactor 
       >> m_params.quadraticAttenuationFactor;
    m_params.type = (LightType)type;
}

void LightNode::write(std::wostream& os) const
{
    TransformNode::write(os);
    os << m_params.type << "\n" << m_params.ambientColor << "\n" << m_params.diffuseColor << "\n"
       << m_params.specularColor << "\n" << m_params.spotCutoff << "\n"
       << m_params.constantAttenuationFactor << "\n" << m_params.linearAttenuationFactor << "\n"
       << m_params.quadraticAttenuationFactor << "\n";
}

